package com.geline.mybatisplus.interceptor.inner;

import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.geline.mybatisplus.annotation.ExtractAfter;
import com.geline.mybatisplus.context.ExtractAfterThreadLocal;
import com.geline.mybatisplus.interceptor.InnerAfterInterceptor;
import com.geline.mybatisplus.util.MapperUtil;
import com.geline.mybatisplus.util.SpELParserUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.springframework.cglib.beans.BeanMap;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.sql.SQLException;

/**
 * mybatis-plus关联表字段回填拦截器: @ExtractAfter
 * 使用方式：interceptor.addInnerInterceptor(new ExtractAfterInterceptor());
 *
 * @author mx
 * @date 2025/9/16
 */
@Slf4j
public class ExtractAfterInterceptor implements InnerAfterInterceptor {
    private ExtractAfterThreadLocal afterThreadLocal;

    public ExtractAfterInterceptor(ExtractAfterThreadLocal threadLocal){
        this.afterThreadLocal = threadLocal;
    }

    @Override
    public boolean willDoUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException, ClassNotFoundException {
        if (ms.getSqlCommandType() == SqlCommandType.DELETE) {//删除之前处理
            if(ms.getId().contains("deleteById")){
                //com.geline.cloud.system.mapper.SysNoticeMapper
                String mapper = ms.getId().replace(".deleteById", "");
                Class<BaseMapper> mapperClass = ClassUtil.loadClass(mapper);
                BaseMapper baseMapper = SpringUtil.getBean(mapperClass);
                Serializable idValue = (Serializable) parameter;
                Object entity = baseMapper.selectById(idValue);
                if(entity!=null) {
                    TableName child = entity.getClass().getAnnotation(TableName.class);
                    String table = child.value();
                    //处理当前子表(存在 @ExtractAfter)
                    Class<?> entityClass = entity.getClass();
                    ExtractAfter[] extractAfters = entityClass.getAnnotationsByType(ExtractAfter.class);
                    if (extractAfters != null && extractAfters.length>0) {
                        String threadKey = table+":"+entityClass.getName();
                        log.info("@ExtractAfter: find keyA = {}", threadKey+":"+idValue);
                        //取Entity对象并设置到当前线程中
                        afterThreadLocal.set(threadKey, idValue, entity);
                    }
                }
            }
        }
        return true;
    }

    @Override
    public boolean afterUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
        Object entity = getEntity(ms, parameter);
        if(entity==null){
            Class<?> entityClazz = getEntityClass(ms);
            if(entityClazz!=null){
                ExtractAfter[] extractAfters = entityClazz.getAnnotationsByType(ExtractAfter.class);
                if (extractAfters != null && extractAfters.length>0) {
                    log.warn("自定义方法[{}]，不能触发@ExtractAfter处理器: {}", ms.getId(), entityClazz.getSimpleName());
                }
            }
        }else {
            Class<?> entityClass = entity.getClass();
            ExtractAfter[] extractAfters = entityClass.getAnnotationsByType(ExtractAfter.class);
            //主表存在@ExtractAfter时，设置到当前线程中
            if (extractAfters != null && extractAfters.length>0) {
                for (ExtractAfter extractAfter : extractAfters){
                    Object fullEntity = getEntityById(entity);
                    String condition = extractAfter.condition();
                    if(StrUtil.isNotBlank(condition)){
                        Boolean execute = SpELParserUtil.executeBoolean(fullEntity, condition);
                        log.debug("fullEntity: {}", JSONUtil.toJsonStr(fullEntity));
                        if(!execute){
                            log.debug("@ExtractAfter[{} : {}=false]", entityClass.getSimpleName(), condition, execute);
                            continue;
                        }else {
                            log.info("@ExtractAfter[{} : {}=true]", entityClass.getSimpleName(), condition);
                        }
                    }

                    TableName child = entity.getClass().getAnnotation(TableName.class);
                    String table = child.value();
                    String threadKey = table+":"+entityClass.getName();
                    Serializable idValue = getIdValue(entity);
                    log.debug("@ExtractAfter: find keyB = {}", threadKey+":"+idValue);
                    //取Entity对象并设置到当前线程中
                    afterThreadLocal.set(threadKey, idValue, fullEntity);
                }
            }
        }
        return true;
    }

    private Serializable getIdValue(Object entity) {
        Serializable idValue = null;
        if (entity != null) {
            idValue = (Serializable) BeanMap.create(entity).get("id");
            if(idValue!=null){
                return idValue;
            }
        }
        Field[] declaredFields = entity.getClass().getDeclaredFields();
        for (Field field : declaredFields) {
            TableId tableId = field.getAnnotation(TableId.class);
            if (tableId != null) {
                idValue = (Serializable) BeanMap.create(entity).get(field.getName());
                break;
            }
        }
        return idValue;
    }

    private Object getEntityById(Object entity) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entity.getClass());
        String currentNamespace = tableInfo.getCurrentNamespace();
        Class<BaseMapper> mainMapperClass = ClassUtil.loadClass(currentNamespace);
        BaseMapper mainMapper = SpringUtil.getBean(mainMapperClass);
        Serializable idValue = getIdValue(entity);
        Object newEntity = mainMapper.selectById(idValue);
        return newEntity;
    }

    private Class getEntityClass(MappedStatement ms) {
        SqlCommandType sct = ms.getSqlCommandType();
        if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.INSERT) {
            String mapperFullClass = ms.getId().substring(0, ms.getId().lastIndexOf("."));
            return MapperUtil.getEntityClass(mapperFullClass);
        }
        return null;
    }

    private Object getEntity(MappedStatement ms, Object parameter) {
        Object entity = null;
        SqlCommandType sct = ms.getSqlCommandType();
        if (sct == SqlCommandType.UPDATE) {
            if(parameter instanceof MapperMethod.ParamMap){
                MapperMethod.ParamMap map = (MapperMethod.ParamMap) parameter;
                if(map.containsKey("et")){
                    entity = map.get("et");
                }
            }
        }if (sct == SqlCommandType.INSERT) {
            entity = parameter;
        }
        return entity;
    }
}
